﻿/**
 * 文件IO工具类: <br />
 * https://www.bilibili.com/video/BV1vs411n7TH/?p=113 <br />
 * https://www.bilibili.com/video/BV1vs411n7TH?p=114 <br />
 * https://www.bilibili.com/video/BV1vs411n7TH?p=121 <br /><br />
 *
 * 导入示例: <b>#include "../Utils/FileIOUtils.h"</b> <br /><br />
 *
 * <table border="2px" bordercolor="red" cellspacing="0px" cellpadding="5px">
 *  <tr align="center">
 *      <th>打开模式</th>
 *      <th>读写方式</th>
 *      <th>打开类型</th>
 *      <th>if(文件∃)</th>
 *      <th>if(文件不∃)</th>
 *      <th>备注(∃: 存在的意思)</th>
 *  </tr>
 *  <tr align="center">
 *      <td>r(rt) 或 rb</td>
 *      <td>只读</td>
 *      <td>文本/二进制</td>
 *      <td></td>
 *      <td>返回NULL</td>
 *      <td></td>
 *  </tr>
 *  <tr align="center">
 *      <td>w(wt) 或 wb</td>
 *      <td>只写</td>
 *      <td>文本/二进制</td>
 *      <td>清空</td>
 *      <td>创建1个</td>
 *      <td>写会覆盖光标后面的字符</td>
 *  </tr>
 *  <tr align="center">
 *      <td>a(at) 或 ab</td>
 *      <td>追加写</td>
 *      <td>文本/二进制</td>
 *      <td></td>
 *      <td>创建1个</td>
 *      <td>只能在文件末尾追加.\n 1.打开后, ftell()光标位置 = 0\n 2.fseek()会失效\n 3.不能读!</td>
 *  </tr>
 *  <tr align="center">
 *      <td>r+(rt+) 或 rb+</td>
 *      <td>读写</td>
 *      <td>文本/二进制</td>
 *      <td></td>
 *      <td>返回NULL</td>
 *      <td>1.读会改变光标位置,导致再次写入会失败\n 2.调用fseek(0);随便传个值0后,又可以继续写了</td>
 *  </tr>
 *  <tr align="center">
 *      <td>w+(wt+) 或 wb+</td>
 *      <td>读写</td>
 *      <td>文本/二进制</td>
 *      <td>清空</td>
 *      <td>创建1个</td>
 *      <td>1.写会覆盖光标后面的字符\n 2.写后要调用fseek()将光标移前or传0,才可以读</td>
 *  </tr>
 *  <tr align="center">
 *      <td>a+(at+) 或 ab+</td>
 *      <td>读写</td>
 *      <td>文本/二进制</td>
 *      <td></td>
 *      <td>创建1个</td>
 *      <td>在文件末尾追加\n 1.打开后, ftell()光标位置 = 0\n 2.读会改变光标位置,导致追加失败.\n 3.调用fseek(0);随便传个值0后,又可以追加了</td>
 *  </tr>
 * </table> <br /> <br />
 *
 *
 *
 *  <b>文件使用方式由 r,w,a,t,b，+ 六个字符拼成，各字符的含义是：</b> <br />
 *  <table border="2px" bordercolor="red" cellspacing="0px" cellpadding="5px">
 *      <tr align="center">
 *          <th>符号</th>
 *          <th>含义</th>
 *      </tr>
 *      <tr>
 *          <td>r(read)</td>
 *          <td>读</td>
 *      </tr>
 *      <tr>
 *          <td>w(write)</td>
 *          <td>写</td>
 *      </tr>
 *      <tr>
 *          <td>a(append)</td>
 *          <td>追加</td>
 *      </tr>
 *      <tr>
 *          <td>t(text)</td>
 *          <td>文本文件，可省略不写</td>
 *      </tr>
 *      <tr>
 *          <td>b(banary)</td>
 *          <td>二进制模式的意思, 仅在Windows有效, 在Linux用 r和rb 的结果一样.</td>
 *      </tr>
 *      <tr>
 *          <td>+</td>
 *          <td>读和写?</td>
 *      </tr>
 *  </table> <br /> <br />
 *
 * <b>注意:</b> <br />
 * 1. b 是二进制模式的意思, b只在Windows有效, 在Linux用 r/rb 是一样的. <br />
 * 2. Unix & Linux 下所以的文本文件都是'\\n'结尾, 而Windows所有的文本文件都是"\\r\\n"结尾. <br />
 * 3. 在Windows下, 打开文本文件不加 b: <br />
 *      &emsp;&emsp; ①.在读取文件时, 系统会将"\\r\\n"转换成'\\n' <br />
 *      &emsp;&emsp; ②.在写入文件时, 系统会将'\\n'转换成"\\r\\n"写入 <br />
 *      &emsp;&emsp; ③.以"二进制"方式打开文件, 则读写'\\n'都不会进行这样的转换. <br />
 * 4. 在Unix/Linux平台下, "文本"&"二进制"模式没区别, "\\r\\n"作为2个字符原样输入输出. <br />
 */

/**
 * C语言中用一个指针变量指向一个文件, 这个指针称为文件指针.
 * FILE是系统使用typedef定义的有关文件信息的一种结构体类型, 包含文件名, 文件状态&当前位置 等信息.
 */

#ifndef __FILE_IO_UTILS__
#define __FILE_IO_UTILS__
#include <stdio.h>
#include <stdarg.h>

/**
 * 以读方式打开文本文件
 * @param path 文件路径说明: <br />
 *             <table>
 *                  <tr>
 *                      <th>No.</th>
 *                      <th>path</th>
 *                      <th>说明</th>
 *                  </tr>
 *                  <tr>
 *                      <td>1</td>
 *                      <td>"F:\\\\test.txt"<br />或"F:/test.txt"</td>
 *                      <td>绝对路径</td>
 *                  </tr>
 *                  <tr>
 *                      <td>2</td>
 *                      <td>"test.txt"</td>
 *                      <td>打开源程序所在目录的文件</td>
 *                  </tr>
 *                  <tr>
 *                      <td>3</td>
 *                      <td>"./test/test.txt"</td>
 *                      <td>打开当前目录下的test文件夹里的test.txt文件</td>
 *                  </tr>
 *                  <tr>
 *                      <td>4</td>
 *                      <td>"../test.txt"</td>
 *                      <td>打开上一级目录的test.txt文件</td>
 *                  </tr>
 *             </table>
 *
 * @return 成功: 文件指针 <br />
 *         失败: NULL, 失败的原因: <br />
 *          1.文件不∃ <br />
 *          2.没有文件读写权限 <br />
 *          3.进程打开文件超出上限[65535(short)个]
 */
FILE* file_open_r(char *path) {
    /**
     * 打开文件
     * FILE *__cdecl fopen(const char * __restrict__ _Filename,const char * __restrict__ _Mode) __MINGW_ATTRIB_DEPRECATED_SEC_WARN;
     * @param _Filename: 文件路径
     * @param _Mode: 打开文件的模式
     */
    return fopen(path, "r");
}

/**
 * 以读方式打开 二进制文件
 * @param path 文件路径
 * @return 成功: 文件指针 <br />
 *         失败: NULL
 */
FILE* file_open_rb(char *path) {
    return fopen(path, "rb");
}

/**
 * 以 可读写 的方式打开 文本文件, if(文件不∃) 返回NULL; <br />
 * 1.读会改变光标位置,导致再次写入会失败. <br />
 * 2.调用fseek(0);随便传个值0后,又可以继续写了
 * @param path 文件路径
 * @return 成功: 文件指针 <br />
 *         失败: NULL
 */
FILE* file_open_r_plus(char *path) {
    return fopen(path, "r+");
}

/**
 * 以写方式打开文本文件, if(文件∃) 清空;   if(文件不∃) 创建1个; <br />
 * 写会覆盖光标后面的字符
 * @param path 文件路径
 * @return 成功: 文件指针 <br />
 *         失败: NULL
 */
FILE* file_open_w(char *path) {
    return fopen(path, "w");
}

/**
 * 以写方式打开 二进制文件, if(文件∃) 清空;   if(文件不∃) 创建1个;
 * @param path 文件路径
 * @return 成功: 文件指针 <br />
 *         失败: NULL
 */
FILE* file_open_wb(char *path) {
    return fopen(path, "wb");
}

/**
 * 以 可读写(w+) 方式打开文本文件, if(文件∃) 清空;   if(文件不∃) 创建1个; <br />
 * 1.写会覆盖光标后面的字符 <br />
 * 2.写后要调用fseek()将光标移前or传0,才可以读
 * @param path 文件路径
 * @return 成功: 文件指针 <br />
 *         失败: NULL
 */
FILE* file_open_w_plus(char *path) {
    return fopen(path, "w+");
}

/**
 * 以 可追加(a) 的方式打开 文本文件, if(文件∃) 在文件末尾追加;   if(文件不∃) 创建1个; <br />
 * 1.打开后, ftell()光标位置 = 0 <br />
 * 2.fseek() 会失效, 只能在文件末尾追加 <br />
 * 3.不能读!
 * @param path 文件路径
 * @return 成功: 文件指针 <br />
 *         失败: NULL
 */
FILE* file_open_a(char *path) {
    return fopen(path, "a");
}

/**
 * 以 可读&可追加(a+) 的方式打开 文本文件, if(文件∃) 在文件末尾追加;   if(文件不∃) 创建1个; <br />
 * 1.打开后, ftell()光标位置 = 0 <br />
 * 2.读会改变光标位置,导致追加失败. <br />
 * 3.调用fseek(0);随便传个值0后,又可以追加了
 * @param path 文件路径
 * @return 成功: 文件指针 <br />
 *         失败: NULL
 */
FILE* file_open_a_plus(char *path) {
    return fopen(path, "a+");
}

/**
 * 关闭文件
 * @param file 文件指针, 可传NULL
 * @return 成功0, 失败-1
 */
int file_close(FILE *file) {
    return fclose(file);
}

/**
 * 从文件中读取1个字符, 读取后指针自动向后+1. <br />
 * 默认读取的编码: ANSI. <br />
 * 文件结尾:
 * @see file_end_of(FILE *readFile)

 * @param file 文件指针
 * @return 成功: 返回读取到的字符.   失败: -1
 */
int file_getc(FILE *file) {
    return fgetc(file);
}

/**
 * 从文件读取字符, 保存到str. 直到出现'换行字符', '文件结尾', 'size-1' 个字符为止. <br />
 * 最后会自动加上字符'\0'作为字符串结束.
 * @param str 字符串 or 字符数组, 字符数组[]的大小和每次读取1行的字符个数相关, 每1行读取完都有1个'换行'
 * @param size 最大读取字符串的长度(size - 1)
 * @param file 文件指针
 * @return 成功: 成功读取到的1行字符串(和str相同) <br />
 *         NULL: 读到文件尾/报错
 */
char* file_gets(char *str, int size, FILE *file) {
    return fgets(str, size, file);
}

/**
 * 从文件中, 按照格式读取, 并给参数地址赋值
 * @param file 文件指针
 * @param format 字符串格式, 例: %d%c%d=\n
 * @param ... 格式字符串的参数地址, 例: &a, %c, %b
 * @return 成功: 成功转换的值的个数 <br />
 *         失败: -1
 */
int file_scanf(FILE *file, const char *format, ...) {
    //成功: 实际写入文件的字符个数.   失败: -1
//    int successSize = fscanf(file, format, ...);
    va_list args;
    va_start(args, format);
//    va_arg()
    /**
     * 将格式化的数据从流中读取到变量参数列表中
     * @return 返回已成功填充的参数列表的数量
     */
    int successSize = vfscanf(file, format, args);
    va_end(args);
    return successSize;
}

/**
 * 以数据块的方式读取'二进制文件'内容 <br />
 * size_t __cdecl fread(void * __restrict__ _DstBuf,size_t _ElementSize,size_t _Count,FILE * __restrict__ _File); <br />
 * 可读取的数据 (<b>注意: 以什么方式存储, 就以什么方式读取, 否则会出错. 比如存储数组, 以字符指针读取就会出错!</b>): <br />
 *      &emsp;&emsp; int a; / char arr[]; / char *str; / char *strs[2]; / struct Student stus[2]; / ...
 * @param _DstBuf  : 准备读取文件数据的地址, 内存卡大小=_ElementSize*_Count, 示例:  &a;  &str;  &stus;
 * @param _ElementSize : 要读取的每个元素的大小(单位Byte), 例: sizeof(int);  strlen(char);  sizeof(struct Student)
 * @param _Count: 要读取元素的个数，每个元素的大小为 _ElementSize 字节. 例: 1;  1024(如果读取字符串, 可写入多个元素)
 * @param _File : 文件指针
 * @return 成功: 实际成功读取到的元素个数, ≤ _Count <br />
 *         失败or读到文件末尾: 0
 */
size_t file_read_binary(void * _DstBuf, size_t _ElementSize, size_t _Count, FILE * _File) {
    return fread(_DstBuf, _ElementSize, _Count, _File);
}

/**
 * 检测是否读取到了文件结尾 <br />
 *      文本文件  : 以 EOF(End of readFile)(-1)结尾, 数据都是ASCII(0~127)码的形式存储 <br />
 *      二进制文件: 由于二进制文件有-1值的情况, ∴ANSI C提供了一个feof()函数用来判断文件是否结束. <br />
 *      feof()既可判断文本文件, 又可判断二进制文件. <br />
 * 注意: <br />
 *      如果读写ANSI编码的文本文件, 用这个函数检测的结尾有问题, 文本文档最好还是用EOF判断!!!
 * @param file 文件指针
 * @return 0: 没有到文件结尾 <br />
 *         非0: 已到文件结尾
 */
int file_eof(FILE *file) {
    return feof(file);
}

/**
 * 将ch转换为unsigned char后写入文件, 写入后指针自动向后+1. <br />
 * 默认写入的编码: ANSI.
 * @param ch 需要写入文件的字符
 * @param file 文件指针. 如果输出到屏幕, 固定写为stdout
 * @return 成功: 写入文件的字符 <br />
 *         失败: -1
 */
int file_putc(int ch, FILE *file) {
    return fputc(ch, file);
}

/**
 * 将str写入到file中, 字符串结束符'\0'不写入文件.
 * @param str 字符串, 写入后, str指针的位置不会改变.
 * @param file 文件指针. 如果输出到屏幕, 固定写为stdout
 * @return 成功0, 失败-1
 */
int file_puts(const char *str, FILE *file) {
    return fputs(str, file);
}

/**
 * 将格式化字符串, 输出到文件. 直到'\0'为止
 * @param file 文件指针
 * @param format 格式化, 例: %d%c%d=\n
 * @param ... 格式化数据, 例: 2, '+', 5
 * @return 成功: 实际写入文件的字符个数 <br />
 *         失败: -1?
 */
int file_printf(FILE *file, const char *format, ...) {
    //成功: 实际写入文件的字符个数.   失败: -1
//    return fprintf(file, format, ...);
    va_list args;
    va_start(args, format);
//    va_arg()
    /**
     * 将格式化后的字符串写入到文件
     * @return 成功: 实际写入文件的字符个数.  失败: 返回负数
     */
    return vfprintf(file, format, args);
    va_end(args);
}

/**
 * 以数据块的方式给文件写入"二进制"内容 <br />
 * size_t __cdecl fwrite(const void * __restrict__ _Str,size_t _Size,size_t _Count,FILE * __restrict__ _File); <br />
 * 可写入的数据 (<b>注意: 以什么方式存储, 就以什么方式读取, 否则会出错. 比如存储数组, 以字符指针读取就会出错!</b>): <br />
 *      &emsp;&emsp; int a; / char arr[]; / char *str; / char *strs[2]; / struct Student stus[2];
 * @param _Str  : 准备写入文件数据的地址,    示例:  &a;  &arr;  &stus;
 * @param _Size : 要写入的每个元素的大小(单位Byte), 例: sizeof(int);  strlen(char);  sizeof(struct Student)
 * @param _Count: 要写入元素的个数，每个元素的大小为 _Size 字节. 例: 1;  sizeof(str)(如果写字符串, 可写入多个元素)
 * @return 成功: 实际成功写入的元素个数 ≤ _Count <br />
 *         失败: 0
 */
size_t file_write_binary(const void *str, size_t size, size_t count, FILE *file) {
    return fwrite(str, size, count, file);
}

/**
 * 检测 读取/写入 文件时, 读取/写入 是否出错.
 * @return 读写没错误0, 读写有错误: 非0值
 */
int file_error(FILE *file) {
    return ferror(file);
}

/**
 * 更新缓冲区, 让缓冲区的数据立马写到文件中. <br />
 * 相当于编辑wps文档后, 按 Ctrl+S 保存键的功能.
 * @return 成功0, 失败-1
 */
int file_flush(FILE *file) {
    return fflush(file);
}

/**
 * 移动文件光标的读写位置
 * int __cdecl fseek(FILE *_File,long _Offset,int _Origin);
 * @param file 文件指针
 * @param offset 根据 _Origin 来移动的偏移量(Byte), 可以是±数. <br />
 *                if是正数: 相对 _Origin 往右移, 否则往左移. <br />
 *                if向前移动的字节数超过了文件开头: 报错! <br />
 *                if向后移动的字节数超过了文件末尾: 再次写入时, 将增大尺寸.
 * @param origin 光标移动前位置: <br />
 *                SEEK_SET: 0, 文件的开头 <br />
 *                SEEK_CUR: 1, 文件指针的当前位置 <br />
 *                SEEK_END: 2, 文件的末尾
 * @return 成功0, 失败-1
 */
int file_seek(FILE *file, long offset, int origin) {
    return fseek(file, offset, origin);
}

/**
 * 获取文件光标 的读写位置
 * @return 成功: 当前文件光标读写位置.  失败: -1
 */
long file_tell(FILE *file) {
    return ftell(file);
}

/**
 * 把文件光标的读写位置 移动到文件开头
 * rewind [ˌriːˈwaɪnd] v.重绕(磁带等);倒带;倒片  n.重绕;倒带器
 */
void file_rewind(FILE *file) {
    rewind(file);
}

/**
 * 直接删除文件, 不会进回收站
 * @param path 文件路径
 * @return 成功0, 失败-1
 */
int file_remove(char *path) {
    return remove(path);
}

/**
 * 改文件名 & 移动文件(剪切) 的功能(新旧文件不在同1目录)
 * @param oldPath 旧文件名
 * @param newPath 新文件名
 * @return 成功0, 失败-1
 */
int file_rename_and_move(const char *oldPath, const char *newPath) {
    return rename(oldPath, newPath);
}



/**
 * 打印文件 <br />
 * @param file
 * 1.以前的FILE定义? <br />
 * typedef struct { <br />
 *   short         level;    //缓冲区"满"or"空"的程度 <br />
 *   unsigned      flags;    //稳健状态标志 <br />
 *   char          fd;       //文件描述符 <br />
 *   unsigned char hold;     //如无缓冲区不读取字符 <br />
 *   short         bsize;    //缓冲区的大小 <br />
 *   unsigned char *buffer;  //数据缓冲区的位置 <br />
 *   unsigned      ar;       //指针, 当前的指向 <br />
 *   unsigned      istemp;   //临时文件, 指示器 <br />
 *   short         token;    //用于有效性的检查 <br />
 * } FILE; <br /> <br />
 * 2.在 stdio.h 中的定义: <br />
 * struct _iobuf { <br />
 *   char *_ptr;        //文件输入的下一个位置 <br />
 *   int _cnt;          //当前缓冲区位置 <br />
 *   char *_base;       //文件起始位置 <br />
 *   int _flag;         //文件标志 <br />
 *   int _file;         //文件有效性验证 <br />
 *   int _charbuf;      //系统为文件读写分配内部缓冲区的状态，如果分配缓冲区失败，则无法读写文件。0表示成功分配，可以正常操作。 <br />
 *   int _bufsiz;       //缓冲区大小, 单位Byte。VC编译器分配的是一个内存页的大小即4KB，4096字节。 <br />
 *   char *_tmpfname;   //临时文件名 <br />
 * }; <br />
 * typedef struct _iobuf FILE; <br />
 * @param isPrintPtr 是否打印当前光标以后的内容, 可能内容有点多
 * @param isPrintBase 是否打印文件开始到结束的内容, 可能内容有点多
 */
void printFile(FILE *file, int isPrintPtr, int isPrintBase) {
    if (file == NULL) return;
    printf("①. _cnt=%d (当前缓冲区位置)\n"
           "②. _flag=%d (文件标志)\n"
           "③. _file=%d (文件有效性验证)\n"
           "④. _charbuf=%d (系统为文件读写分配内部缓冲区的状态，如果分配缓冲区失败，则无法读写文件。0表示成功分配，可以正常操作。)\n"
           "⑤. _bufsiz=%d (缓冲区大小, 单位Byte。VC编译器分配的是一个内存页的大小即4KB，4096字节。)\n"
           "⑥. _tmpfname=%s (临时文件名)\n",
           file->_cnt, file->_flag, file->_file, file->_charbuf,
           file->_bufsiz, file->_tmpfname);
    //打印当前光标以后的内容, 可能内容有点多
    if(isPrintPtr) printf("⑦. _ptr=%s (文件输入的下一个位置)\n", file->_ptr);
    //文件开始到结束的内容, 可能内容有点多
    if(isPrintBase) printf("⑧. _base(文件起始位置)=%s", file->_base);
}


/**
 * 获取文件状态信息
 * @param path 文件路径
 * @param _Stat 保存文件信息的结构体, 具体信息见↓: 初始化示例并传入指针: struct stat stat;  &stat; <br />
 * 如果这儿传入的是对象, 结果不正确..
 *  @see printFileState(struct stat)
 *
 * @return 成功0, 失败-1
 */
//#include <sys/types.h>
#include <sys/stat.h>
int file_stat(const char *path, struct stat *_Stat) {
    //int __cdecl stat(const char *_Filename,struct stat *_Stat);
    return stat(path, _Stat);
}

/**
 * @param stat 文件状态 <br />
 * struct stat {
 *   _dev_t st_dev;             //1.文件的设备编号
 *   _ino_t st_ino;             //2.节点
 *   unsigned short st_mode;    //3.文件的类型&存取的权限
 *   short st_nlink;            //4.连到该文件的硬连接数目, 刚建立的文件=1
 *   short st_uid;              //5.用户ID
 *   short st_gid;              //6.组ID
 *   _dev_t st_rdev;            //7.设备类型. if此文件为设备文件, 则为其设备编号
 *   _off_t st_size;            //8.文件字节数(文件大小)
 *   unsigned long st_blksize;  //9.块大小(文件系统的I/O缓冲区大小)
 *   unsigned long st_blocks;   //10.块数
 *   time_t st_atime;           //11.最后1次访问时间
 *   time_t st_mtime;           //12.最后1次修改时间
 *   time_t st_ctime;           //13.最后1次改变时间(创建时间)
 * };
 */
void printFileState(struct stat stat) {
//    if (stat == NULL) return;
    printf("①. st_dev=%u (文件的设备编号)\n"
           "②. st_ino=%hu (节点)\n"
           "③. st_mode=%hu (文件的类型&存取的权限)\n"
           "④. st_nlink=%hd (连到该文件的硬连接数目)\n"
           "⑤. st_uid=%hd (用户ID)\n"
           "⑥. st_gid=%hd (组ID)\n"
           "⑦. st_rdev=%u (设备类型. if此文件为设备文件, 则为其设备编号)\n"
           "⑧. st_size=%ld Byte (文件大小)\n"

//           "⑨. st_blksize=%lu (块大小(文件系统的I/O缓冲区大小))\n"
//           "⑩. st_blocks=%lu (块数)\n"

           "⑾. st_atime=%ld (最后1次访问时间)\n"       //写⑪报错, 编码原因
           "⑿. st_mtime=%ld (最后1次修改时间)\n"       //⑫
           "⒀. st_ctime=%ld (最后1次改变时间(创建时间))\n"//⑬
           ,
           //对象打印方式
           stat.st_dev, stat.st_ino, stat.st_mode, stat.st_nlink,
           stat.st_uid, stat.st_gid, stat.st_rdev, stat.st_size
//           , stat.st_blksize, stat.st_blocks    //未找到这2个属性
           , stat.st_atime, stat.st_mtime, stat.st_ctime);

            //指针打印方式
//           stat->st_dev, stat->st_ino, stat->st_mode, stat->st_nlink,
//           stat->st_uid, stat->st_gid, stat->st_rdev, stat->st_size
////           , stat->st_blksize, stat->st_blocks    //未找到这2个属性
//           , stat->st_atime, stat->st_mtime, stat->st_ctime);
}

/**
 * 文件拷贝
 * @param fromPath 从哪儿拷贝
 * @param toPath 要拷贝到哪儿去
 * @param isPrintLog 是否打印日志
 * @return 成功0, 失败-1
 */
#include <stdlib.h>
int file_copy(char *fromPath, char* toPath, int isPrintLog) {
    int resule = 0;
    char *buf = NULL;
    FILE *readFile = NULL, *writeFile = NULL;
    //1.文件状态
    struct stat stat;
    int fail0 = file_stat(fromPath, &stat);
    if (fail0) {
        if (isPrintLog) printf("文件拷贝, 获取文件状态信息失败: %s\n", fromPath);
        resule = fail0;
        goto CLOSE_AND_RETURN;
    }
    //打印文件状态
    if (isPrintLog) printFileState(stat);
    //有0Byte的文件!! 不能往下走下去, 否则COUNT=0会死循环!!!
    if (stat.st_size <= 0) {
        resule = 0;
        goto CLOSE_AND_RETURN;
    }
    readFile = file_open_rb(fromPath);
    writeFile = file_open_wb(toPath);
    if (!readFile || !writeFile) {
        if (isPrintLog) {
            if (!readFile) printf("文件拷贝, 打开源文件失败: %s\n", fromPath);
            if (!writeFile) printf("文件拷贝, 打开/创建目标文件失败: %s\n", toPath);
        }
        resule = -1;
        goto CLOSE_AND_RETURN;
    }

    //2.根据实际文件大小开辟空间. 如果文件<8Mb, 开辟实际大小
    static const int ELEMENT_SIZE = sizeof(char);
    static int COUNT = 1024 * 1024 * 8;   //每次读写8Mb个cha
    if (stat.st_size <= COUNT) {
        //多留一点空间, 否则会读第2次&读第2次的元素个数=0
        COUNT = stat.st_size + sizeof(double);
    } else if (stat.st_size % COUNT == 0) {
        //防止文件大小刚好是COUNT的整数倍, 导致读最后1次的长度=0
        COUNT += 1;
    }
    buf = malloc(ELEMENT_SIZE * COUNT);
    if (buf == NULL) {
        if (isPrintLog) printf("文件拷贝, malloc(%d)(单位Byte)申请内存失败\n", ELEMENT_SIZE * COUNT);
        resule = -1;
        goto CLOSE_AND_RETURN;
    }

    //3.读写
    int count0, count1;
    while (!file_eof(readFile)) {
//        memset(buf, 0, ELEMENT_SIZE * COUNT);
        //读取到的元素个数
        count0 = file_read_binary(buf, ELEMENT_SIZE, COUNT, readFile);
        if (count0 <= 0) {
            //如果读写出错
            int fail1 = file_error(readFile);
            if (fail1) {
                if (isPrintLog) printf("文件拷贝, 读取数据块失败: %d个, fail1 = %d\n", count0, fail1);
                resule = -1;
                goto CLOSE_AND_RETURN;
            } else {
                if (isPrintLog) printf("读没出错, 但count0 = %d\n", count0);
//                resule = 0;
//                goto CLOSE_AND_RETURN;
            }
        }
//        if (count0 != COUNT && isPrintLog) {
//            printf("最后1次读取, 读取到的元素个数=%d, strlen(buf)=%d\n", count0, strlen(buf));
//        }
        //写入
        count1 = file_write_binary(buf, ELEMENT_SIZE, count0, writeFile);
        if (count1 <= 0) {
            //如果读写出错
            int fail2 = file_error(readFile);
            if (fail2) {
                if (isPrintLog) printf("文件拷贝, 写入数据块失败: %d个, fail2 = %d\n", count1, fail2);
                resule = -1;
                goto CLOSE_AND_RETURN;
            } else {
                if (isPrintLog) printf("写没出错, 但count1 = %d\n", count1);
//                resule = 0;
//                goto CLOSE_AND_RETURN;
            }
        }
    }
    if (isPrintLog) printf("拷贝完成, 源文件: %s, 拷贝后: %s\n", fromPath, toPath);
    CLOSE_AND_RETURN:
    free(buf);
    file_close(readFile);
    file_close(writeFile);
    return resule;
}

//Others...
__attribute__((unused)) static void others(char *format, ...) {
    va_list args;
    va_start(args, format);
//    va_arg()
    
    /**
     * 从标准输入读取数据(stdin), 将格式化的数据读入变量参数列表
     * @return
     */
    int i = vscanf(format, args);
    /**
     * 将格式化的数据从字符串读取到变量参数列表中
     * @param source: 源字符串
     * @return 如果成功，则返回写入的字符总数，否则返回一个负数
     */
    int len1 = vsscanf("2+5=\n", format, args);


    /**
     * 使用参数列表发送格式化输出到标准输出 stdout, 和printf()作用差不多
     * @param format: 例: %d+%d=\n
     * @return 成功，则返回写入的字符总数?，否则返回一个负数
     */
    int size = vprintf(format, args);
    va_end(args);
}
#endif